TypeScript 55.4%
Python 43.2%
SQL 1.2%
1import Link from "next/link";2import type { Metadata } from "next";3import { notFound } from "next/navigation";4import { EventRow } from "@/components/event-row";5import { LiveFeed } from "@/components/live-feed";6import { Badge, Bar, Chip, Empty, PageHeader, Panel, Score, Sparkline, Table, Td, TierBadge } from "@/components/ui";7import { api } from "@/lib/api";8import { CHANNELS, CHANNEL_KEYS, feedHref, fmtInt, typeLabel } from "@/lib/format";910export const dynamic = "force-dynamic";1112/** Categories that are valid desks even when they have no recent event. */13const KNOWN = new Set<string>([...CHANNEL_KEYS, "cloud", "developer", "consumer-tech", "semiconductors", "pharma", "statistics", "space", "automotive", "commerce", "payments", "crypto", "enterprise", "internet", "standards", "technology", "media", "politics", "sports", "energy", "transport", "telecom", "education", "retail", "gaming", "web-policy", "packages", "api"]);1415function channelOf(channel: string) {16 return CHANNELS.find((c) => c.key === channel && c.key !== "all" && c.key !== "breaking" && c.key !== "silent");17}1819export async function generateMetadata({ params }: { params: Promise<{ channel: string }> }): Promise<Metadata> {20 const { channel } = await params;21 const label = channelOf(channel)?.label ?? channel;22 return { title: `${label} desk`, description: `Live ${label} events detected by WebSensor: breaking, silent changes, active sources and trending entities.`, alternates: { canonical: `/category/${channel}` } };23}2425/** Specialized real-time desk (spec §103). */26export default async function CategoryPage({ params }: { params: Promise<{ channel: string }> }) {27 const { channel } = await params;28 if (!/^[a-z0-9-]{2,32}$/.test(channel)) notFound();29 const desk = await api.categoryDesk(channel);30 if (!desk || (desk.recent.length === 0 && !KNOWN.has(channel))) notFound();31 const chan = channelOf(channel);32 const label = chan?.label ?? channel;33 const series = desk.series.map((p) => p.n);34 const total48 = series.reduce((a, b) => a + b, 0);35 const maxType = Math.max(1, ...desk.by_type.map((t) => t.n));36 const maxSrc = Math.max(1, ...desk.active_sources.map((s) => s.events_24h));37 return (38 <>39 <PageHeader40 compact41 kicker={42 <span className="flex flex-wrap items-center gap-1.5">43 <span>Desk</span>44 <span className="text-fg-subtle">·</span>45 {desk.categories.map((c) => (46 <Chip key={c} href={`/category/${c}`} tone={c === channel ? "signal" : "default"}>{c}</Chip>47 ))}48 </span>49 }50 title={label}51 description={`Meaningful changes from sources categorized as ${desk.categories.map((c) => `“${c}”`).join(", ")} — official pages, feeds, status pages, documentation, filings.`}52 actions={53 <div className="flex items-center gap-3">54 <div className="text-right">55 <div className="label">48 h</div>56 <div className="font-mono text-base font-semibold tabular">{fmtInt(total48)}</div>57 </div>58 <Sparkline values={series} width={160} height={36} tone="signal" />59 </div>60 }61 />62 <div className="grid gap-4 xl:grid-cols-[1fr_340px]">63 <div className="min-w-0">64 <LiveFeed initial={desk.recent} initialCursor={desk.nextCursor} fixed={chan ? chan.key : undefined} extraQuery={chan ? undefined : { category: channel }} showTabs={false} title={`${label.toUpperCase()} · LIVE`} />65 </div>66 <aside className="flex min-w-0 flex-col gap-4">67 <Panel title={<span className="text-hot">Breaking · 24 h</span>} dense action={<Link href={feedHref({ category: channel, signal_min: 80 }, "/live")} className="text-[11px] text-fg-subtle hover:text-fg">live →</Link>}>68 {desk.breaking.length ? desk.breaking.slice(0, 8).map((e) => <EventRow key={e.id} ev={e} />) : <Empty>Nothing is breaking in this desk right now.</Empty>}69 </Panel>70 <Panel title={<span className="text-silent">Silent</span>} dense action={<Link href={feedHref({ category: channel, silent_change: true }, "/live")} className="text-[11px] text-fg-subtle hover:text-fg">all →</Link>}>71 {desk.silent.length ? desk.silent.slice(0, 8).map((e) => <EventRow key={e.id} ev={e} />) : <Empty>No silent change in this desk.</Empty>}72 </Panel>73 <Panel title="Active sources · 24 h" dense action={<Link href={`/sources?category=${channel}`} className="text-[11px] text-fg-subtle hover:text-fg">sources →</Link>}>74 {desk.active_sources.length ? (75 <Table head={["Source", "Kind", "24 h", "", "Max"]}>76 {desk.active_sources.map((s) => (77 <tr key={s.id} className="hover:bg-panel-2/60">78 <Td>79 <span className="flex items-center gap-1.5"><TierBadge tier={s.tier} /><Link href={`/source/${s.id}`} className="truncate font-medium hover:underline">{s.name}</Link></span>80 <div className="truncate font-mono text-[10.5px] text-fg-subtle">{s.domain}</div>81 </Td>82 <Td>{s.first_party === false ? <Badge kind="external" compact /> : <Badge kind="first-party" compact />}</Td>83 <Td mono>{fmtInt(s.events_24h)}</Td>84 <Td><div className="w-12"><Bar value={s.events_24h} max={maxSrc} /></div></Td>85 <Td><Score value={s.max_importance} size="sm" /></Td>86 </tr>87 ))}88 </Table>89 ) : (90 <Empty>No source produced an event in this desk in the last 24 h.</Empty>91 )}92 </Panel>93 <Panel title="Trending entities · 24 h" dense action={<Link href="/explore?tab=trending" className="text-[11px] text-fg-subtle hover:text-fg">all →</Link>}>94 {desk.trending_entities.length ? (95 <ol className="divide-y divide-line">96 {desk.trending_entities.map((t, i) => (97 <li key={t.id} className="grid grid-cols-[1.25rem_1fr_auto] items-center gap-2 px-3 py-1.5 text-[13px]">98 <span className="font-mono text-[11px] text-fg-subtle tabular">{i + 1}</span>99 <div className="min-w-0">100 <Link href={`/entity/${t.id}`} className="block truncate font-medium hover:underline">{t.name}</Link>101 <div className="truncate text-[11px] text-fg-subtle">{t.type.replace(/_/g, " ")} · {t.sources} source{t.sources === 1 ? "" : "s"}</div>102 </div>103 <span className="font-mono text-[12px] font-semibold tabular">{fmtInt(t.events_24h)}</span>104 </li>105 ))}106 </ol>107 ) : (108 <Empty>No entity is trending in this desk.</Empty>109 )}110 </Panel>111 <Panel title="By type · 7 d" dense>112 {desk.by_type.length ? (113 <ul className="divide-y divide-line">114 {desk.by_type.map((t) => (115 <li key={t.event_type} className="grid grid-cols-[8.5rem_1fr_3rem] items-center gap-2 px-3 py-1.5 text-[12.5px]">116 <Link href={feedHref({ category: channel, event_type: t.event_type }, "/live")} className="truncate hover:underline">{typeLabel(t.event_type)}</Link>117 <Bar value={t.n} max={maxType} tone="info" />118 <span className="text-right font-mono text-fg-subtle tabular">{fmtInt(t.n)}</span>119 </li>120 ))}121 </ul>122 ) : (123 <Empty />124 )}125 </Panel>126 </aside>127 </div>128 </>129 );130}131